import { useEffect, useState } from "react"; import { FlagIcon, ShareIcon } from "@heroicons/react/24/solid"; import Details from "@/components/watch/primary/details"; import EpisodeLists from "@/components/watch/secondary/episodeLists"; import { getServerSession } from "next-auth"; import { useWatchProvider } from "@/lib/context/watchPageProvider"; import { authOptions } from "../../../api/auth/[...nextauth]"; import { getRemovedMedia } from "@/prisma/removed"; import { createList, createUser, getEpisode } from "@/prisma/user"; import Link from "next/link"; import MobileNav from "@/components/shared/MobileNav"; import { Navbar } from "@/components/shared/NavBar"; import Modal from "@/components/modal"; import AniList from "@/components/media/aniList"; import { signIn } from "next-auth/react"; import BugReportForm from "@/components/shared/bugReport"; import Skeleton from "react-loading-skeleton"; import Head from "next/head"; import VidStack from "@/components/watch/new-player/player"; import { useRouter } from "next/router"; import { Spinner } from "@vidstack/react"; import RateModal from "@/components/shared/RateModal"; export async function getServerSideProps(context) { let userData = null; const session = await getServerSession(context.req, context.res, authOptions); const accessToken = session?.user?.token || null; const query = context?.query; if (!query) { return { notFound: true }; } let proxy; proxy = process.env.PROXY_URI || null; if (proxy && proxy.endsWith("/")) { proxy = proxy.slice(0, -1); } const disqus = process.env.DISQUS_SHORTNAME || null; const [aniId, provider] = query?.info; const watchId = query?.id; const epiNumber = query?.num; const dub = query?.dub; const removed = await getRemovedMedia(); const isRemoved = removed?.find((i) => +i?.aniId === +aniId); if (isRemoved) { return { redirect: { destination: "/en/removed", permanent: false } }; } const ress = await fetch(`https://graphql.anilist.co`, { method: "POST", headers: { "Content-Type": "application/json", ...(accessToken && { Authorization: `Bearer ${accessToken}` }) }, body: JSON.stringify({ query: `query ($id: Int) { Media (id: $id) { mediaListEntry { progress status customLists repeat } id idMal title { romaji english native } status genres episodes studios { edges { node { id name } } } bannerImage description coverImage { extraLarge color } synonyms } } `, variables: { id: aniId } }) }); const data = await ress.json(); // const variables = { id: aniId }; // const data = await getAnilistMediaInfo(variables, context.req); try { if (session) { await createUser(session.user.name); await createList(session.user.name, watchId); const data = await getEpisode(session.user.name, watchId); userData = JSON.parse( JSON.stringify(data, (key, value) => { if (key === "createdDate") { return String(value); } return value; }) ); } } catch (error) { console.error(error); // Handle the error here } return { props: { sessions: session, provider: provider || null, watchId: watchId || null, epiNumber: epiNumber || null, dub: dub || null, userData: userData?.[0] || null, info: data?.data?.Media || null, proxy, disqus } }; } export default function Watch({ info, watchId, disqus, proxy, dub, userData, sessions, provider, epiNumber }) { const [artStorage, setArtStorage] = useState(null); const [episodeNavigation, setEpisodeNavigation] = useState(null); const [episodesList, setepisodesList] = useState(); const [mapEpisode, setMapEpisode] = useState(null); const [open, setOpen] = useState(false); const [isOpen, setIsOpen] = useState(false); const { setAutoNext, ratingModalState, setRatingModalState } = useWatchProvider(); const [onList, setOnList] = useState(false); const router = useRouter(); const { theaterMode, setPlayerState, setAutoPlay, setMarked, setTrack, aspectRatio, setDataMedia } = useWatchProvider(); useEffect(() => { async function getInfo() { if (info.mediaListEntry) { setOnList(true); } setDataMedia(info); const response = await fetch( `/api/v2/episode/${info.id}?releasing=${ info.status === "RELEASING" ? "true" : "false" }${dub ? "&dub=true" : ""}` ).then((res) => res.json()); const getMap = response.find((i) => i?.map === true) || response[0]; let episodes = response; if (getMap) { if (provider === "gogoanime" && !watchId.startsWith("/")) { episodes = episodes.filter((i) => { if (i?.providerId === "gogoanime" && i?.map !== true) { return null; } return i; }); } setMapEpisode(getMap?.episodes); } if (episodes) { const getProvider = episodes?.find((i) => i.providerId === provider); const episodeList = getProvider?.episodes.slice( 0, getMap?.episodes.length ); const playingData = getMap?.episodes.find( (i) => i.number === Number(epiNumber) ); if (getProvider) { setepisodesList(episodeList); const currentEpisode = episodeList?.find( (i) => i.number === parseInt(epiNumber) ); const nextEpisode = episodeList?.find( (i) => i.number === parseInt(epiNumber) + 1 ); const previousEpisode = episodeList?.find( (i) => i.number === parseInt(epiNumber) - 1 ); const vidNav = { prev: previousEpisode, playing: { id: currentEpisode.id, title: playingData?.title || info?.title?.romaji, description: playingData?.description, img: playingData?.img || playingData?.image, number: currentEpisode.number }, next: nextEpisode }; setEpisodeNavigation(vidNav); } } setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); // setEpiData(episodes); } getInfo(); return () => { setEpisodeNavigation(null); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessions?.user?.name, epiNumber, dub]); useEffect(() => { const autoNext = localStorage.getItem("autoNext"), autoPlay = localStorage.getItem("autoplay"); if (autoNext) { setAutoNext(autoNext); } if (autoPlay) { setAutoPlay(autoPlay); } async function fetchData() { if (info) { const anify = await fetch("/api/v2/source", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source: provider === "gogoanime" && !watchId.startsWith("/") ? "consumet" : "anify", providerId: provider, watchId: watchId, episode: epiNumber, id: info.id, sub: dub ? "dub" : "sub" }) }).then((res) => res.json()); if (!anify?.sources?.length > 0) { router.push(`/en/anime/${info.id}?notfound=true`); return; } const skip = await fetch( `https://api.aniskip.com/v2/skip-times/${info.idMal}/${parseInt( epiNumber )}?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=` ).then((res) => { if (!res.ok) { switch (res.status) { case 404: { return null; } } } return res.json(); }); let getOp = skip?.results?.find((item) => item.skipType === "op") || null, getEd = skip?.results?.find((item) => item.skipType === "ed") || null; const op = getOp ? { startTime: anify?.intro?.start ?? Math.round(getOp?.interval.startTime), endTime: anify?.intro?.end ?? Math.round(getOp?.interval.endTime), text: "Opening" } : null, ed = { startTime: anify?.outro?.start ?? Math.round(getEd?.interval.startTime), endTime: anify?.outro?.end ?? Math.round(getEd?.interval.endTime), text: "Ending" }; const skipData = [op, ed].filter((i) => i !== null); const quality = anify?.sources?.find( (i) => i.quality === "default" || i.quality === "auto" ) || anify?.sources[0]; const reFormSubtitles = anify?.subtitles?.map((i) => { return { src: proxy + "/" + i.url, label: i.lang, kind: i.lang === "Thumbnails" ? "thumbnails" : "subtitles", ...(i.lang === "English" && { default: true }) }; }); const thumbnails = reFormSubtitles?.find( (i) => i.kind === "thumbnails" ); const subtitles = reFormSubtitles?.filter( (i) => i.kind !== "thumbnails" ); const episode = { provider, isDub: dub, defaultQuality: { // url: quality?.url, url: `${proxy}/proxy/m3u8/${encodeURIComponent( String(quality?.url) )}/${encodeURIComponent(JSON.stringify(anify?.headers))}`, headers: anify?.headers }, subtitles: subtitles, thumbnails: thumbnails?.src, epiData: anify, skip: skipData }; setTrack(episode); } } fetchData(); return () => { setPlayerState({ currentTime: 0, isPlaying: false }); setMarked(0); setTrack(null); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [provider, watchId, info?.id]); useEffect(() => { const mediaSession = navigator.mediaSession; if (!mediaSession) return; const now = episodeNavigation?.playing; const poster = now?.img || info?.bannerImage; const title = now?.title || info?.title?.romaji; const artwork = poster ? [{ src: poster, sizes: "512x512", type: "image/jpeg" }] : undefined; mediaSession.metadata = new MediaMetadata({ title: title, artist: `Moopa ${ title === info?.title?.romaji ? "- Episode " + epiNumber : `- ${info?.title?.romaji || info?.title?.english}` }`, artwork }); }, [episodeNavigation, info, epiNumber]); const handleShareClick = async () => { try { if (navigator.share) { await navigator.share({ title: `Watch Now - ${info?.title?.english || info.title.romaji}`, // text: `Watch [${info?.title?.romaji}] and more on Moopa. Join us for endless anime entertainment"`, url: window.location.href }); } else { // Web Share API is not supported, provide a fallback or show a message alert("Web Share API is not supported in this browser."); } } catch (error) { console.error("Error sharing:", error); } }; function handleOpen() { setOpen(true); document.body.style.overflow = "hidden"; } function handleClose() { setOpen(false); document.body.style.overflow = "auto"; } return ( <> {episodeNavigation?.playing?.title || `${info?.title?.romaji} - Episode ${epiNumber}`} handleClose()}> {!sessions && (

Edit your list

)}
{!ratingModalState.isFullscreen && ( )}
{theaterMode && (
{episodeNavigation ? ( ) : (
)}
)}
{!theaterMode && (
{episodeNavigation ? ( ) : (
)}
)}
{(episodeNavigation?.playing?.title || info.title.romaji) ?? "Loading..."}

{episodeNavigation?.playing?.number ? ( `Episode ${episodeNavigation?.playing?.number}` ) : ( )}

handleOpen()} disqus={disqus} />
); } function SpinLoader() { return (
); }